Unity multiplayer game on GameLift #3
Unityで作成したマルチプレイヤーゲームをGameLiftで動かしてみました!! このシリーズでは、GameLiftを利用するために必要なアプリケーション側での振る舞いをサンプルコードを交えながら紹介していきます。 ということで前回に続く、第3回の記事となります。
範囲
*の箇所が第3回の範囲です。
- 全体構成の説明
- クライアントサイドアプリ実装のポイント
- *サーバーサイドアプリ実装のポイント
- API Gateway + Lambda実装のポイント
- GameLiftへのデプロイ
- ゲームプレイ
サーバーサイドアプリ実装のポイント
Unity公式チュートリアルのマルチプレーヤゲームをGameLiftに適応させるには、サーバーサイドアプリとしていくつかの通信/処理を実装する必要があります。
Launch server processにおいて必要な通信/処理
- 1.GameLiftServiceから起動されServerSDKの初期化処理を実行する
- 2.GameLiftServiceにProcessReadyであることを通知する
- 3.GameLiftServiceに定期的にプロセスの状態を通知する
Start gameにおいて必要な通信/処理
- 4.Gameのサーバーサイドアプリケーションを起動する
Add playerにおいて必要な通信/処理
- 5.GameLiftServiceにPlayerが追加されたことを通知する
Drop playerにおいて必要な通信/処理
- 6.GameLiftServiceにPlayerが削除されたことを通知する
Stop gameにおいて必要な通信/処理
- 7.GameLiftServiceにGameが終了したことを通知する
Shut down server processにおいて必要な通信/処理
- 8.GameLiftServiceにGameLiftのprocessが終了したことを通知する
1〜4までの処理はサーバー起動時に実行する、または、サーバー起動時のコールバック関数として実装する箇所。5〜8まではアプリケーション内のイベントに応じて実行する箇所となります。 実現方法は色々ありますが、この通信/処理を満たすよう設計/実装することが、ゲーム(サーバーサイドアプリ)をGameLiftに適用させるためのポイントになります。 なお、この通信/処理を実現するためにはServerSDKをUnityプロジェクトに統合する必要があります。まだ実施されいない方は下記ブログを参考に実施してください。
サーバーサイドアプリ修正
ということで、実際にチュートリアルのマルチプレーヤゲームをGameLiftに適用させてみました。 実施した処理は、大きく分けて以下の2つです。
- C#スクリプトの追加/修正
- GameObjectの追加
C#スクリプトの追加/修正
3つのC#スクリプトを追加/修正します。
- GameLift.cs
- MyNetworkManager.cs
- PlayerController.cs
GameLift.cs
サーバーサイドアプリ起動時に実行される処理です。「1.GameLiftServiceから起動されServerSDKの初期化処理を実行する」の処理は26行目、「2.GameLiftServiceにProcessReadyであることを通知する」の処理は49行目で実施しています。コールバック関数は29行目ProcessParameters
にて実装しており、第1引数がOnStartGameSession(「4.Gameのサーバーサイドアプリケーションを起動する」)
、第2引数がOnProcessTerminate
、第3引数がOnHealthCheck(「3.GameLiftServiceに定期的にプロセスの状態を通知する」)
、第4引数がport
、第5引数が logParameters
となります。
また、このサーバーサイドアプリでは起動時にはリスニングポートを指定できるようにしています。このように実装している理由は第5回のブログで紹介します。
using UnityEngine; using UnityEngine.Networking; using System.Collections.Generic; using Aws.GameLift.Server; using Aws.GameLift.Server.Model; public class GameLift : MonoBehaviour { public static int listeningPort = 7777; void Awake() { } public void Start() { string[] args = System.Environment.GetCommandLineArgs(); int len = args.Length; for (int i = 0; i < args.Length; i++) { Debug.Log(args[i]); if (args [i] == "-listeningPort") { listeningPort = int.Parse(args[i+1]); } } var initSDKOutcome = GameLiftServerAPI.InitSDK(); if (initSDKOutcome.Success) { ProcessParameters processParameters = new ProcessParameters( (gameSession) => { NetworkManager.singleton.networkAddress = "localhost"; NetworkManager.singleton.networkPort = listeningPort; NetworkManager.singleton.StartServer (); GameLiftServerAPI.ActivateGameSession(); }, () => { GameLiftServerAPI.ProcessEnding(); }, () => { return true; }, listeningPort, new LogParameters(new List<string>() { "/local/game/logs" }) ); var processReadyOutcome = GameLiftServerAPI.ProcessReady(processParameters); if (processReadyOutcome.Success) { print("ProcessReady success."); } else { print("ProcessReady failure : " + processReadyOutcome.Error.ToString()); } } else { print("InitSDK failure : " + initSDKOutcome.Error.ToString()); } } void OnApplicationQuit() { GameLiftServerAPI.Destroy(); } }
MyNetworkManager.cs
NetworkManagerを継承したクラスです。NetworkManagerにはoerride
可能なメソッドがいくつか用意されており、OnServerAddPlayer
、OnServerDisconnect
もその一つです。5.GameLiftServiceにPlayerが追加されたことを通知するの処理はOnServerAddPlayer
メソッド(15行目)にて実装しています。この関数は、クライアントがサーバーに接続した際にサーバーサイドで呼び出されます。関数内ではクライアントサイドが送信したplayerSessionId
を取得後、AcceptPlayerSession
を実行し、GameLiftServiceにプレイヤーが追加されたことを通知します。
また、プレイヤー追加時には、connectionId
をキーにplayerSessionId
をplayerSessionData
に登録し現在このゲームセッションに接続中のプレイヤーを管理します。
6.GameLiftServiceにPlayerが削除されたことを通知するの処理はOnServerDisconnect
メソッド(24行目)にて実装しています。この関数は、クライアントが切断された際にサーバーサイドで呼び出されます。関数内ではconnectionId
をキーにplayerSessionId
を取得後、RemovePlayerSession
を実行し、GameLiftServiceにプレイヤーが削除されたことを通知します。
using UnityEngine; using UnityEngine.Networking; using System.Collections.Generic; using UnityEngine.Networking.NetworkSystem; using Aws.GameLift.Server; public class MyNetworkManager : NetworkManager { public static Dictionary<int, string> playerSessionData = new Dictionary<int, string>(); public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader) { Debug.Log("OnServerAddPlayer"); var playerSessionId = extraMessageReader.ReadMessage<StringMessage>().value; GameLiftServerAPI.AcceptPlayerSession(playerSessionId); base.OnServerAddPlayer(conn,playerControllerId); playerSessionData.Add(conn.connectionId,playerSessionId); } public override void OnServerDisconnect(NetworkConnection conn) { Debug.Log("OnServerDisconnect"); string playerSessionId = playerSessionData[conn.connectionId]; GameLiftServerAPI.RemovePlayerSession(playerSessionId); playerSessionData.Remove(conn.connectionId); base.OnServerDisconnect(conn); } public override void OnClientConnect(NetworkConnection conn) { #クライアントサイドで実行される処理 } }
PlayerController.cs
プレイヤーの操作を司るクラスです。今回はこのクラスに「プレイヤーがQ
を押した時にゲームを終了する」処理を追加します。7.GameLiftServiceにGameが終了したことを通知するの処理を61行目、8.GameLiftServiceにGameLiftのprocessが終了したことを通知するの処理を62行目でそれぞれ実装しています。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking; using Aws.GameLift.Server; using Aws.GameLift.Server.Model; public class PlayerController : NetworkBehaviour { public GameObject bulletPrefab; public Transform bulletSpawn; void Update() { if (!isLocalPlayer) { return; } var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f; var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f; transform.Rotate(0, x, 0); transform.Translate(0, 0, z); if (Input.GetKeyDown(KeyCode.Space)) { CmdFire(); } if (Input.GetKeyDown(KeyCode.Q)) { CmdStopGame(); } } [Command] void CmdFire() { var bullet = (GameObject)Instantiate( bulletPrefab, bulletSpawn.position, bulletSpawn.rotation); bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6; NetworkServer.Spawn(bullet); Destroy(bullet, 2.0f); } public override void OnStartLocalPlayer() { GetComponent<Renderer>().material.color = Color.blue; } [Command] private void CmdStopGame() { NetworkManager.singleton.StopServer (); GameLiftServerAPI.TerminateGameSession(); GameLiftServerAPI.ProcessEnding(); } }
GameObjectの追加
空のゲームオブジェクトを追加し、先ほど作成したGameLift.cs
をアタッチします。
まとめ
GameLiftを利用するためのサーバーサイドアプリの実装のポイントを確認しました。次回はAPI Gateway + Lambda実装のポイントを紹介しようと思います。